﻿using NAudio.Wave;
using SoundEngine3;
using SoundEngine3.AudioIO;
using SoundEngine3.AudioProcessing;
using SoundEngine3.DspPlugins.Meta;
using System;
using System.Collections.Generic;
using System.Threading;

namespace Zquence_Studio_3_WindowsDesktop.SoundEngine
{
    public class AsioNAudio : BaseAudioDriver
    {
        private AsioOut m_currentDriver;
        private BufferedWaveProvider mCurrentBufferedProvider;
        private int m_last_buffer_size;
        private int m_last_min_buffer_size;
        private int m_last_max_buffer_size;
        private int m_last_input_channels;
        private int m_last_output_channels;
        private double m_last_sampleRate = 44100;

        private object mUIControl;
        
        public override int MinimumBufferSize { get { return m_last_min_buffer_size; } }

        public override int MaximumBufferSize { get { return m_last_max_buffer_size; } }

        public override int PreferredBufferSize { get { return m_last_buffer_size; } }

        public override double SampleRate { get { return m_last_sampleRate; } }

        public override int InputChannels
        {
            get { return m_currentDriver.NumberOfInputChannels; }
        }

        public override int OutputChannels
        {
            get { return m_currentDriver.NumberOfOutputChannels; }
        }

        public override bool Started
        {
            get
            {
                return m_currentDriver.PlaybackState == PlaybackState.Playing;
            }
        }

        public AsioNAudio()
            : base("Asio (NAudio)")
        {
            foreach (var driver in AsioOut.GetDriverNames())
            {
                SoundDevice device = new SoundDevice(driver, this, driver);
                AddSoundDevice(device);
            }
        }

        public override void SetSoundDevice(SoundDevice soundDevice)
        {
            if (m_currentDriver != null)
            {
                mCurrentBufferedProvider.ClearBuffer();
                mCurrentBufferedProvider.DiscardOnBufferOverflow = true;
                mCurrentBufferedProvider = null;

                //m_currentDriver.Stop();
                m_currentDriver.Dispose();
                m_currentDriver.AudioAvailable -= driver_BufferUpdate;
                m_currentDriver = null;

                // TODO: Kraschar här ofta. Fixa!
                //       Verkar ha någonting med ASIO att göra när man stänger ner ZS3.
                GC.Collect();
            }

            if (soundDevice != null)
            {
                try
                {
                    string asioDevice = soundDevice.GetDeviceIdentifier<string>();
                    m_currentDriver = new AsioOut(asioDevice);

                    WaveFormat waveFormat = new WaveFormat(44100, 16, m_currentDriver.DriverOutputChannelCount);
                    mCurrentBufferedProvider = new BufferedWaveProvider(waveFormat);

                    m_currentDriver.InitRecordAndPlayback(mCurrentBufferedProvider, m_currentDriver.DriverInputChannelCount, 44100);
                    m_currentDriver.AudioAvailable += driver_BufferUpdate;

                    string lbl =
@"
Output channels: {0}
Input channels: {1}
Latency: {2} ms
";

#if WPF
                    info.Text = string.Format(lbl,
                        m_currentDriver.DriverOutputChannelCount,
                        m_currentDriver.DriverInputChannelCount,
                        m_currentDriver.PlaybackLatency);
#endif

                    SetNewSampleRate(44100);
                }
                catch (Exception)
                {
                    throw new AudioDriverException("AsioDriver could not be initialized.");
                }
            }
        }

        public override void Initialize(int bufferSize, int sampleRate)
        {

        }

        void SoundEngineAdapter_OnSampleRateChanged(double sampleRate)
        {
            AudioInput.SampleRate = sampleRate;
            AudioOutput.SampleRate = sampleRate;

            RaiseOnSampleRateChanged(sampleRate);
        }


        void SoundEngineAdapter_OnBufferSizeChanged(int min, int max, int preferred)
        {
            foreach (var channel in AudioInput.Channels)
            {
                channel.SetBufferSize(preferred);
            }
            foreach (var channel in AudioOutput.Channels)
            {
                channel.SetBufferSize(preferred);
            }

            RaiseOnBufferSizeChanged(min, max, preferred);
        }



        unsafe void driver_BufferUpdate(object sender, AsioAudioAvailableEventArgs e)
        {
            // the driver is the sender
            AsioOut driver = sender as AsioOut;

            if (mCurrentBufferedProvider == null)
            {
                e.WrittenToOutputBuffers = false;
                return;
            }

            // Check change of input channels.
            if (driver.NumberOfInputChannels != m_last_input_channels)
            {
                // No messing, this is high priority stuff.
                Thread.CurrentThread.Priority = ThreadPriority.Highest;

                m_last_input_channels = driver.NumberOfInputChannels;

                // Set input channels.
                AudioInput.SetChannels(m_last_input_channels, e.SamplesPerBuffer);

                // Copy input channels.
                for (int i = driver.NumberOfInputChannels - 1; i >= 0; i--)
                {
                    //void* array = e.InputBuffers[i].ToPointer();
                    string name = m_currentDriver.AsioInputChannelName(i);

                    // Set input channel name.
                    AudioInput.Channels[i].Name = name;

                    // Add audio input class.
                    AudioInput audioInput = new AudioInput(this, AudioInput.Channels[i], name);
                    AddInput(audioInput);
                }

                RaiseOnInputChannelChanged(m_last_input_channels);
            }

            // Check change of output channels.
            if (driver.NumberOfOutputChannels != m_last_output_channels)
            {
                // No messing, this is high priority stuff.
                Thread.CurrentThread.Priority = ThreadPriority.Highest;

                m_last_output_channels = driver.NumberOfOutputChannels;

                AudioOutput.SetChannels(m_last_output_channels, e.SamplesPerBuffer);

                for (int i = AudioOutput.Channels.Length - 1; i >= 0; i--)
                {
                    // Set output channel name.
                    AudioOutput.Channels[i].Name = driver.AsioOutputChannelName(i);
                }

                RaiseOnOutputChannelChanged(m_last_output_channels);
            }

            // Check change of buffer size.
            if (e.SamplesPerBuffer != m_last_buffer_size)
            {
                // No messing, this is high priority stuff.
                Thread.CurrentThread.Priority = ThreadPriority.Highest;

                m_last_buffer_size = e.SamplesPerBuffer;
                m_last_min_buffer_size = e.SamplesPerBuffer;
                m_last_max_buffer_size = e.SamplesPerBuffer;

                // Never check if null. This class will always connect this event.
                SoundEngineAdapter_OnBufferSizeChanged(m_last_min_buffer_size, m_last_max_buffer_size, m_last_buffer_size);
            }

            // Update the state.
            if (m_last_sampleRate != 44100)
            {
                // No messing, this is high priority stuff.
                Thread.CurrentThread.Priority = ThreadPriority.Highest;

                SetNewSampleRate(44100);
            }

            // Clear output audio.
            foreach (var channel in AudioOutput.Channels)
            {
                Array.Clear(channel.Samples, 0, channel.Samples.Length);
            }
            // Copy input audio.
            for (int i = driver.NumberOfInputChannels - 1; i >= 0; i--)
            {
                switch (e.AsioSampleType)
                {
                    case NAudio.Wave.Asio.AsioSampleType.Int16MSB:
                        break;
                    case NAudio.Wave.Asio.AsioSampleType.Int24MSB:
                        break;
                    case NAudio.Wave.Asio.AsioSampleType.Int32MSB:
                        break;
                    case NAudio.Wave.Asio.AsioSampleType.Float32MSB:
                        fixed (double* inputArray = AudioInput.Channels[i].Samples)
                        {
                            float* array = (float*)e.InputBuffers[i].ToPointer();

                            for (int j = PreferredBufferSize - 1; j >= 0; j--)
                            {
                                inputArray[j] = array[j];
                            }
                        }
                        break;
                    case NAudio.Wave.Asio.AsioSampleType.Float64MSB:
                        break;
                    case NAudio.Wave.Asio.AsioSampleType.Int32MSB16:
                        break;
                    case NAudio.Wave.Asio.AsioSampleType.Int32MSB18:
                        break;
                    case NAudio.Wave.Asio.AsioSampleType.Int32MSB20:
                        break;
                    case NAudio.Wave.Asio.AsioSampleType.Int32MSB24:
                        break;
                    case NAudio.Wave.Asio.AsioSampleType.Int16LSB:
                        break;
                    case NAudio.Wave.Asio.AsioSampleType.Int24LSB:
                        break;
                    case NAudio.Wave.Asio.AsioSampleType.Int32LSB:
                        fixed (double* inputArray = AudioInput.Channels[i].Samples)
                        {
                            int* array = (int*)e.InputBuffers[i].ToPointer();
                        
                            for (int j = PreferredBufferSize - 1; j >= 0; j--)
                            {
                                double val = array[j] / (double)int.MaxValue;

                                inputArray[j] = val;
                            }
                        }

                        //System.Runtime.InteropServices.Marshal.Copy(e.InputBuffers[i], AudioInput.Channels[i].Samples, 0, e.SamplesPerBuffer);
                        //System.Runtime.InteropServices.Marshal.Copy(buf, 0, e.OutputBuffers[i], e.SamplesPerBuffer);

                        break;
                    case NAudio.Wave.Asio.AsioSampleType.Float32LSB:
                        break;
                    case NAudio.Wave.Asio.AsioSampleType.Float64LSB:
                        break;
                    case NAudio.Wave.Asio.AsioSampleType.Int32LSB16:
                        break;
                    case NAudio.Wave.Asio.AsioSampleType.Int32LSB18:
                        break;
                    case NAudio.Wave.Asio.AsioSampleType.Int32LSB20:
                        break;
                    case NAudio.Wave.Asio.AsioSampleType.Int32LSB24:
                        break;
                    case NAudio.Wave.Asio.AsioSampleType.DSDInt8LSB1:
                        break;
                    case NAudio.Wave.Asio.AsioSampleType.DSDInt8MSB1:
                        break;
                    case NAudio.Wave.Asio.AsioSampleType.DSDInt8NER8:
                        break;
                    default:
                        break;
                }

                // Copy input audio to soundengine3 channel.
                
            }

            // Trigger the on buffer event.
            RaiseOnBufferUpdate(AudioOutput);

            // Copy audio to the speakers.
            for (int i = driver.NumberOfOutputChannels - 1; i >= 0; i--)
            {
                int* array = (int*)e.OutputBuffers[i].ToPointer();

                fixed (double* completeOutputArray = AudioOutput.Channels[i].Samples)
                {
                    for (int j = AudioOutput.Channels[i].Samples.Length - 1; j >= 0; j--)
                    {
                        double doubleValue = completeOutputArray[j];
                        int value = (int)(doubleValue * int.MaxValue * 0.5);
                        array[j] = value;
                    }
                }
            }

            e.WrittenToOutputBuffers = true;
        }

        private unsafe void SetNewSampleRate(int sampleRate)
        {
            m_last_sampleRate = sampleRate;
            AudioInput.SampleRate = sampleRate;
            AudioOutput.SampleRate = sampleRate;
            SoundEngineAdapter_OnSampleRateChanged(sampleRate);
        }
        
        public override void Start()
        {
            m_currentDriver.Play();
        }

        public override void Stop()
        {
            m_currentDriver.Stop();
        }

        public override void Dispose()
        {
            // Release ASIO.
            if (m_currentDriver != null)
            {
                m_currentDriver.Dispose();
            }
        }

        public override void GetGUIControls(List<ParameterProperties> uiControls)
        {
            ButtonParameter asioSettings = new ButtonParameter(null, false);
            asioSettings.Toggle = false;
            asioSettings.ButtonText = "Open ASIO settings";
            asioSettings.OnButtonValueChanged += delegate (bool state)
            {
                if (state)
                {
                    m_currentDriver.ShowControlPanel();
                }
            };
            uiControls.Add(asioSettings);
        }
    }
}
